using System;
using System.Collections.Generic;

namespace ExtractItAll.Core
{
  public struct ProgressInformation
  {
    public int Completed;
    public string CurrentItem;
    public int Total;

    public int GetPercentage()
    {
      decimal percentage = 0;

      if (Completed != -1
          && Total != -1
          && Total != 0)
      {
        percentage = (Completed / (decimal)Total) * 100;

        if (percentage < 0)
        {
          percentage = 0;
        }
        if (percentage > 100)
        {
          percentage = 100;
        }
      }

      return (int)percentage;
    }
  }

  /// <summary>
  /// Represents the method that handles changes to the list,
  /// e.g. a new task was added or removed.
  /// </summary>
  /// <param name="sender">Manager instance.</param>
  public delegate void TasksChangedEventHandler(TaskManager sender);


  /// <summary>
  /// Represents the method that handles updates to a single task,
  /// e.g. changes to status.
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="task"></param>
  public delegate void TaskUpdatedEventHandler(TaskManager sender, TaskItem task);


  /// <summary>
  /// Represents the method that handles progress updates.
  /// </summary>
  /// <param name="sender">Manager instance.</param>
  /// <param name="percentage">Progress in percent.</param>
  public delegate void ProgressChangedEventHandler(ProgressInformation info);


  /// <summary>
  /// Keeps track of all tasks and manages their execution.
  /// </summary>
  public class TaskManager
  {
    private TaskExecutor _ActiveTask;
    private int _Open;
    private bool _Started;
    private List<TaskItem> _Tasks;


    /// <summary>
    /// Creates new instance of <see cref="TaskManager" />.
    /// </summary>
    public TaskManager()
    {
      _Tasks = new List<TaskItem>();
      _ActiveTask = null;
      _Started = false;
      _Open = 0;
    }

    /// <summary>
    /// Gets number of tasks.
    /// </summary>
    public int Count
    {
      get { return _Tasks.Count; }
    }


    /// <summary>
    /// Gets active task.
    /// </summary>
    public TaskItem Active
    {
      get { return _ActiveTask.Task; }
    }


    /// <summary>
    /// Gets task at specified index.
    /// </summary>
    /// <param name="index">0-based index.</param>
    /// <returns>Task at specified index.</returns>
    public TaskItem Get(int index)
    {
      lock (_Tasks)
      {
        if (index > (_Tasks.Count - 1))
          return new TaskItem();

        return _Tasks[index];
      }
    }


    /// <summary>
    /// Gets task with specified id.
    /// </summary>
    /// <param name="id">Task id.</param>
    /// <returns>Task with specified id or if not found empty instance.</returns>
    public TaskItem Get(Guid id)
    {
      TaskItem result;
      lock (_Tasks)
      {
        result = _Tasks.Find(TaskItem.GetById(id));
      }
      if (result == null)
      {
        result = new TaskItem();
      }
      return result;
    }


    /// <summary>
    /// Gets index of task.
    /// </summary>
    /// <param name="task">Task to get index for.</param>
    /// <returns>0-based index or -1 if not found.</returns>
    public int GetIndex(TaskItem task)
    {
      lock (_Tasks)
      {
        int index = _Tasks.FindIndex(TaskItem.GetById(task.Id));
        return index;
      }
    }


    /// <summary>
    /// Adds passed task to work queue.
    /// </summary>
    /// <param name="item">Task to add.</param>
    public void Add(TaskItem item)
    {
      lock (_Tasks)
      {
        if (!_Tasks.Exists(TaskItem.GetByArchive(item.Archive.FileName)))
        {
          _Tasks.Add(item);
          _Open += 1;
        }
      }
      RaiseTasksChanged();

      RaiseTotalProgressChanged();
      ProcessTasks();
    }


    /// <summary>
    /// Resets all failed tasks to open.
    /// </summary>
    public void Reset()
    {
      lock (_Tasks)
      {
        foreach (TaskItem task in _Tasks)
        {
          if (task.IsFailure() ||
              task.IsResumeable())
          {
            task.Status = TaskStatus.Open;
          }
        }
      }
      RaiseTasksChanged();
      RaiseTotalProgressChanged();
      ProcessTasks();
    }


    /// <summary>
    /// Resets specified task to open if not running.
    /// </summary>
    /// <param name="id">Task to reset.</param>
    public void Reset(Guid id)
    {
      TaskItem task;
      lock (_Tasks)
      {
        task = _Tasks.Find(TaskItem.GetById(id));


        if (task == null)
        {
          return;
        }
        if (task.Status == TaskStatus.Open)
        {
          return;
        }
        if (task.IsRunning())
        {
          return;
        }

        task.Status = TaskStatus.Open;
      }
      RaiseTaskUpdated(task);
      RaiseTotalProgressChanged();
      ProcessTasks();
    }


    /// <summary>
    /// Removes single task from list and, if done, deletes archives.
    /// </summary>
    /// <param name="id">Task to remove.</param>
    public void Remove(Guid id)
    {
      Remove(id, false, false);
    }

    /// <summary>
    /// Removes single task from list and, if done, deletes archives.
    /// </summary>
    /// <param name="id">Task to remove.</param>
    /// <param name="deleteArchives">Set to true to delete extracted archives.</param>
    /// <param name="noBin">Set to true to delete permanently.</param>
    public void Remove(Guid id, bool deleteArchives, bool noBin)
    {
      lock (_Tasks)
      {
        TaskItem task;
        task = _Tasks.Find(TaskItem.GetById(id));
        if (task == null)
        {
          return;
        }
        //if (task.IsFinished() && deleteArchives)
        //{
        //  DeleteArchives(task, noBin);
        //}
        if (task.Status == TaskStatus.Open)
        {
          _Open -= 1;
        }
        _Tasks.Remove(task);
      }
      RaiseTasksChanged();

      RaiseTotalProgressChanged();
    }


    /// <summary>
    /// Cancels the specified task if it is running.
    /// </summary>
    /// <param name="id">Task to cancel.</param>
    public void Cancel(Guid id)
    {
      lock (_Tasks)
      {
        TaskItem task;
        task = _Tasks.Find(TaskItem.GetById(id));
        if (task == null)
        {
          return;
        }
        if (!task.IsRunning())
        {
          return;
        }
        if (_ActiveTask != null && task != _ActiveTask.Task)
        {
          return;
        }
        _ActiveTask.Cancel();
      }
    }


    ///// <summary>
    ///// Removes finished tasks.
    ///// </summary>
    ///// <param name="deleteArchives">Set to true to delete extracted archives.</param>
    ///// <param name="noBin">Set to true to delete permanently.</param>
    //public void Clean(bool deleteArchives, bool noBin)
    //{
    //  lock (_Tasks)
    //  {
    //    for (int i = _Tasks.Count - 1; i >= 0; i--)
    //    {
    //      TaskItem task = _Tasks[i];
    //      if (task.IsFinished())
    //      {
    //        _Tasks.RemoveAt(i);
    //        if (deleteArchives)
    //        {
    //          DeleteArchives(task, noBin);
    //        }
    //      }
    //    }
    //  }
    //  RaiseTotalProgressChanged();
    //  RaiseTasksChanged();
    //}


    ///// <summary>
    ///// Deletes archives of passed task.
    ///// </summary>
    ///// <param name="task">Task whose archives will be deleted.</param>
    ///// <param name="noBin">Set to true to delete permanently.</param>
    //private void DeleteArchives(TaskItem task, bool noBin)
    //{
    //  //string path = Path.GetDirectoryName(task.Archive);
    //  //string search = Path.GetFileNameWithoutExtension(task.Archive);
    //  //search = Path.GetFileNameWithoutExtension(search); // removes the .part1
    //  //string[] archives = Directory.GetFiles(path, search + "*.rar", SearchOption.TopDirectoryOnly);
    //  //foreach (string archive in archives)
    //  //{
    //  //  if (noBin)
    //  //  {
    //  //    File.Delete(Path.Combine(path, archive));
    //  //  }
    //  //  else
    //  //  {
    //  //    Win32.MoveToBin(Path.Combine(path, archive));
    //  //  }
    //  //}
    //}


    /// <summary>
    /// Starts processing tasks.
    /// </summary>
    /// <returns>0 if successful, otherwise an error code.</returns>
    public void Start()
    {
      if (_Started)
      {
        return;
      }
      _Started = true;
      lock (_Tasks)
      {
        foreach (TaskItem task in _Tasks)
        {
          if (task.IsResumeable())
          {
            task.Status = TaskStatus.Open;
          }
        }
      }
      RaiseTasksChanged();
      ProcessTasks();
    }


    /// <summary>
    /// Stops processing tasks.
    /// </summary>
    public void Stop()
    {
      if (!_Started)
      {
        return;
      }
      _Started = false;
      if (_ActiveTask != null)
      {
        _ActiveTask.Cancel();
      }
    }


    /// <summary>
    /// Looks if there's anything to do and executes it.
    /// </summary>
    private void ProcessTasks()
    {
      if (!_Started)
      {
        return;
      }
      if (_ActiveTask != null)
      {
        return;
      }

      lock (_Tasks)
      {
        foreach (TaskItem item in _Tasks)
        {
          if (item.IsExecutable())
          {
            _ActiveTask = new TaskExecutor(this, item);
            break;
          }
        }
      }

      if (_ActiveTask != null)
      {
        _ActiveTask.Task.Status = TaskStatus.Working;
        RaiseTaskUpdated(_ActiveTask.Task);
        _ActiveTask.Execute();
      }
    }


    /// <summary>
    /// Called by executor to tell manager task has finished.
    /// </summary>
    internal void TaskFinished()
    {
      _ActiveTask.Task.Status = TaskStatus.Done;
      _Open -= 1;
      RaiseTaskUpdated(_ActiveTask.Task);
      _ActiveTask = null;

      RaiseTotalProgressChanged();
      ProcessTasks();
    }


    /// <summary>
    /// Called by executor to tell manager task was stopped, but can be
    /// resumed when cause is fixed.
    /// </summary>
    internal void TaskWarning(string message)
    {
      _ActiveTask.Task.Status = TaskStatus.Warning;
      _ActiveTask.Task.StatusMessage = message;
      RaiseTaskUpdated(_ActiveTask.Task);
      _ActiveTask = null;

      ProcessTasks();
    }


    /// <summary>
    /// Called by executor to tell manager task was stopped and can not
    /// be fixed.
    /// </summary>
    internal void TaskError(string message)
    {
      _ActiveTask.Task.Status = TaskStatus.Error;
      _ActiveTask.Task.StatusMessage = message;
      RaiseTaskUpdated(_ActiveTask.Task);
      _ActiveTask = null;

      ProcessTasks();
    }


    /// <summary>
    /// Raised when changes to tasks occurred.
    /// </summary>
    public event TasksChangedEventHandler OnTasksChanged;

    private void RaiseTasksChanged()
    {
      if (OnTasksChanged != null)
      {
        OnTasksChanged(this);
      }
    }


    /// <summary>
    /// Raised when task was updated.
    /// </summary>
    public event TaskUpdatedEventHandler OnTaskUpdated;

    private void RaiseTaskUpdated(TaskItem task)
    {
      if (OnTaskUpdated != null)
      {
        OnTaskUpdated(this, task);
      }
    }


    /// <summary>
    /// Raised when progress on task changed.
    /// </summary>
    public event ProgressChangedEventHandler OnFileProgressChanged;

    internal void RaiseFileProgressChanged(ProgressInformation info)
    {
      if (OnFileProgressChanged != null)
      {
        OnFileProgressChanged(info);
      }
    }


    /// <summary>
    /// Raised when progress on task changed.
    /// </summary>
    public event ProgressChangedEventHandler OnArchiveProgressChanged;

    internal void RaiseArchiveProgressChanged(ProgressInformation info)
    {
      if (OnArchiveProgressChanged != null)
      {
        OnArchiveProgressChanged(info);
      }
    }


    /// <summary>
    /// Raised when total progress changed.
    /// </summary>
    public event ProgressChangedEventHandler OnTotalProgressChanged;

    private void RaiseTotalProgressChanged()
    {
      if (OnTotalProgressChanged != null)
      {
        int count = _Tasks.Count;
        int done = count - _Open;

        ProgressInformation info = new ProgressInformation();
        info.Total = count;
        info.Completed = done;

        OnTotalProgressChanged(info);
      }
    }
  }
}